#ifndef SPARK_PARTICLE_HEADER
#define SPARK_PARTICLE_HEADER

#include <iostream>

#include <osg/Version>
#include <osg/Texture>
#include <osg/Drawable>
#include <osg/Transform>
#include <osgDB/XmlParser>
#include <osgGA/GUIEventHandler>

#include "spark/SPK.h"
#include "spark/SPK_GL.h"

namespace nwCore
{

/** The spark drawable contains one or more particle systems of the same base type */
class SparkDrawable : public osg::Drawable
{
public:
    class DeferredSystemHandler : public osg::Drawable::UpdateCallback
    {
    public:
        virtual void update( osg::NodeVisitor* nv, osg::Drawable* drawable );
        
        struct PosAndRotate
        {
            SPK::Vector3D position;
            SPK::Vector3D rotationAxis;
            float rotationAngle;
        };
        std::vector<PosAndRotate> _newSystemsToAdd;
    };
    
    struct SortParticlesOperator
    {
        SortParticlesOperator( const osg::Vec3d& eye )
        { _eye.x = eye.x(); _eye.y = eye.y(); _eye.z = eye.z(); }
        
        virtual bool operator()( SPK::System* lhs, SPK::System* rhs )
        {
            return SPK::getSqrDist(lhs->getWorldTransformPos(), _eye) >
                   SPK::getSqrDist(rhs->getWorldTransformPos(), _eye);
        }
        
        SPK::Vector3D _eye;
    };
    
    struct ImageAttribute
    {
        osg::ref_ptr<osg::Image> image;
        GLuint type;
        GLuint clamp;
    };
    
    typedef std::vector<SPK::System*> ParticleSystemList;
    typedef std::map<std::string, ImageAttribute> TextureObjMap;
    typedef std::map<std::string, GLuint> TextureIDMap;
    typedef SPK::SPK_ID (*CreateBaseSystemFunc)( const SparkDrawable*, const TextureIDMap&, int, int );
    
public:
    SparkDrawable();
    SparkDrawable( const SparkDrawable& copy, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY );
    META_Object( osg, SparkDrawable )
    
    bool isValid() const { return getBaseSystemID()!=SPK::NO_ID && _protoSystem!=NULL; }
    unsigned int getNumParticles() const;
    
    void setBaseSystemCreator( CreateBaseSystemFunc func, bool useProtoSystem=false )
    { _baseSystemCreator = func; _useProtoSystem = useProtoSystem; _dirty = true; }
    
    void setBaseSystemID( SPK::SPK_ID id ) { _baseSystemID = id; }
    SPK::SPK_ID getBaseSystemID() const { return _baseSystemID; }
    
    void setSortParticles( bool b ) { _sortParticles = b; }
    bool getSortParticles() const { return _sortParticles; }
    
    void setAutoUpdateBound( bool b ) { _autoUpdateBound = b; }
    bool getAutoUpdateBound() const { return _autoUpdateBound; }
    
    void setUpdatingActive( bool b ) { _updatingActive = b; }
    bool getUpdatingActive() const { return _updatingActive; }
    
    void setDrawingActive( bool b ) { _drawingActive = b; }
    bool getDrawingActive() const { return _drawingActive; }
    
    SPK::System* getProtoSystem() { return _protoSystem; }
    const SPK::System* getProtoSystem() const { return _protoSystem; }
    
    SPK::System* getParticleSystem( unsigned int i ) { return _particleSystems[i]; }
    const SPK::System* getParticleSystem( unsigned int i ) const { return _particleSystems[i]; }
    unsigned int getNumParticleSystems() const { return _particleSystems.size(); }
    
    void addExternalParticleSystem( SPK::System* system )
    { if (system) _particleSystems.push_back(system); }
    
    void destroyParticleSystem( SPK::System* system, bool removeFromList=true )
    {
        if (system) SPK_Destroy(system);
        if (removeFromList)
        {
            unsigned int i = 0, size = _particleSystems.size();
            for (; i<size; ++i) { if (_particleSystems[i]==system) break; }
            if (i<size) _particleSystems.erase(_particleSystems.begin() + i);
        }
    }
    
    void setGlobalTransformMatrix( const osg::Matrix& matrix, bool useOffset=false );
    void setTransformMatrix( SPK::System* system, const SPK::Vector3D& pos, const SPK::Vector3D& rot,
                             float angle, bool useOffset=false );
    
    /** Add a new system cloned from the base system and return the index for deferred use */
    unsigned int addParticleSystem( const osg::Vec3& p=osg::Vec3(), const osg::Quat& r=osg::Quat() );
    
    /** Add an image for the creator func to use, must be done before the creator func starting */
    void addImage( const std::string& name, osg::Image* image, GLuint type=GL_RGB, GLuint clamp=GL_CLAMP );
    
    /** Update the system, will be called by the SparkUpdatingHandler */
    virtual bool update( double currentTime, const osg::Vec3d& eye );
    
    void genTextureID() const;

    void addImageLater( const std::string& name, const std::string& file, GLuint type=GL_RGB, GLuint clamp=GL_CLAMP );
    GLuint getTextureID( const std::string& texName );
    std::string getTextureNameByID( GLuint texID );

#if OSG_MIN_VERSION_REQUIRED(3,3,2)
    virtual osg::BoundingSphere computeBound() const;
#else
    virtual osg::BoundingBox computeBound() const;
#endif
    virtual void drawImplementation( osg::RenderInfo& renderInfo ) const;
    
protected:
    virtual ~SparkDrawable();
    
    SPK::System* createParticleSystem( const SPK::Vector3D& pos, const SPK::Vector3D& rot, float angle );
    GLuint compileInternalTexture( osg::Image* image, GLuint type, GLuint clamp ) const;
    void convertData( osg::Image* image, GLuint type, unsigned int numCurrent, unsigned int numRequired ) const;
    
    TextureObjMap _textureObjMap;
    mutable TextureIDMap _textureIDMap;
    mutable bool _compileAddedImage;
    ParticleSystemList _particleSystems;
    CreateBaseSystemFunc _baseSystemCreator;
    mutable SPK::SPK_ID _baseSystemID;
    
    mutable SPK::System* _protoSystem;
    mutable unsigned int _activeContextID;
    double _lastTime;
    bool _sortParticles;
    bool _useProtoSystem;
    bool _autoUpdateBound;
    bool _updatingActive;
    bool _drawingActive;
    mutable bool _dirty;
};

/** The spark updater which record all SparkDrawables and update them */
class SparkUpdatingHandler : public osgGA::GUIEventHandler
{
public:
    SparkUpdatingHandler() {}
    
    void addSpark( SparkDrawable* spark, osg::Transform* trackee=0 )
    { _sparks.push_back(SparkObject(spark, trackee)); }
    
    void removeSpark( unsigned int i )
    { if (i<_sparks.size()) _sparks.erase(_sparks.begin()+i); }
    
    void removeSpark( SparkDrawable* spark )
    {
        unsigned int i = 0, size = _sparks.size();
        for (; i<size; ++i) { if (_sparks[i]._spark==spark) break; }
        if (i<size) _sparks.erase(_sparks.begin() + i);
    }
    
    void setTrackee( unsigned int i, osg::Transform* t )
    { _sparks[i]._trackee = t; _sparks[i]._dirtyMatrix = true; }
    
    void setTrackee( SparkDrawable* spark, osg::Transform* t );
    
    osg::Transform* getTrackee( unsigned int i ) { return _sparks[i]._trackee.get(); }
    const osg::Transform* getTrackee( unsigned int i ) const { return _sparks[i]._trackee.get(); }
    
    SparkDrawable* getSpark( unsigned int i ) { return _sparks[i]._spark.get(); }
    const SparkDrawable* getSpark( unsigned int i ) const { return _sparks[i]._spark.get(); }
    unsigned int getNumSparks() const { return _sparks.size(); }
    
    virtual bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa );
    
protected:
    osg::Matrix computeTransformMatrix( SparkDrawable* spark, osg::Transform* trackee );
    
    struct SparkObject
    {
        SparkObject( SparkDrawable* s, osg::Transform* t )
        : _spark(s), _trackee(t), _dirtyMatrix(true) {}
        
        osg::observer_ptr<SparkDrawable> _spark;
        osg::observer_ptr<osg::Transform> _trackee;  // The trackee will be followed by the spark
        osg::Matrix _transformMatrix;  // The matrix from trackee coordinate to spark coordinate
        bool _dirtyMatrix;  // Dirty the matrix if trackee is changed in the scene graph
    };
    std::vector<SparkObject> _sparks;
};

/** Helper group for scene collision test */
class CollisionGroup : public SPK::Group
{
    SPK_IMPLEMENT_REGISTERABLE( CollisionGroup )
public:
    struct Result : public osg::Referenced
    {
        Result() : _collidedNumber(0) {}
        osg::Vec3 _lastCenter;
        int _collidedNumber;
    };
    
    CollisionGroup( SPK::Model* model, size_t capacity );
    CollisionGroup( const CollisionGroup& copy );
    
    // Set collision information for callback to work
    void setCollideData( const osg::Vec3& result, const osg::Vec3& dir, bool valid );
    const osg::Vec3& getCollidedResult() const { return _result->_lastCenter; }
    bool isCollided() const;
    
    // Callback for Spark use
    static bool checkParticleCollision( SPK::Particle& p, float deltaTime );
    
protected:
    osg::ref_ptr<Result> _result;
    osg::Vec3 _colliderCenter, _colliderDir;
    bool _canCollide;
};

}

#endif
